本系列文章主要分析 JsBridge 框架的原理,学习 H5 和原生 WebView 的交互方式,框架选自 GitHub 上的很火的 H5 + WebView 三方库:lzyzsd/JsBridge,作者是大鬼头;
1 调用接口
在 js 中,我们通过如下方式,使用 jsBridge 框架来和 Native 通信:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var data = {id: 1, content: "这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"};
window.WebViewJavascriptBridge.send( data , function(responseData) { document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData } );
window.WebViewJavascriptBridge.callHandler( 'submitFromWeb' , {'param': '中文测试'} , function(responseData) { document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData } );
|
- send 方法;用客户端默认的 handler 处理;
- callHandler 方法:用指定的 handler 处理;
下面我们来分析下 callHandler 和 send 方法!
2 WebViewJavascriptBridge
接下来进入了 js 通信协议文件中:
2.1 send
用客户端默认的 handler 处理
1 2 3 4 5 6
| function send(data, responseCallback) { _doSend({ data: data }, responseCallback); }
|
2.2 callHandler
用指定的 handler 处理
1 2 3 4 5 6 7
| function callHandler(handlerName, data, responseCallback) { _doSend({ handlerName: handlerName, data: data }, responseCallback); }
|
2.3 _doSend
最后都调用了 _doSend 的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function _doSend(message, responseCallback) { if (responseCallback) { var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
|
这里在前面分析过了,和前面的类似。
messagingIframe.src 最终会触发如下方法调用链:
1
| BridgeWebViewClient.shouldOverrideUrlLoading ---> BridgeWebView.flushMessageQueue
|
2.4 _fetchQueue
从 sendMessageQueue 队列中获取 message,发送给 native:
1 2 3 4 5 6 7 8 9 10 11
| function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue); sendMessageQueue = []; if (messageQueueString !== '[]') { bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); } }
|
这里生成了一个新的 url:
yy://://return/_fetchQueue/[{“responseId”:”xxxxxxx”,”responseData”:”xxxxxxxxxx”}]
messagingIframe.src 最终会触发如下方法调用链:
1
| BridgeWebViewClient.shouldOverrideUrlLoading ---> BridgeWebView.handlerReturnData
|
2.5 _handleMessageFromNative
js 代码中会处理 native 发送的 message json:
1 2 3 4 5 6 7 8 9 10
| function _handleMessageFromNative(messageJSON) { console.log(messageJSON); if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON); } _dispatchMessageFromNative(messageJSON); }
|
在第二篇 js 协议中有讲过:
当在动态注入 js 脚本时,会执行 init 方法,那里会将 receiveMessageQueue 置为 null,同时处理已经包含的 native 消息;
所以这里就直接 _dispatchMessageFromNative 了;
2.6 _dispatchMessageFromNative
js 处理 native 层的回调消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function _dispatchMessageFromNative(messageJSON) { setTimeout(function() { var message = JSON.parse(messageJSON); var responseCallback; if (message.responseId) { responseCallback = responseCallbacks[message.responseId]; if (!responseCallback) { return; } responseCallback(message.responseData); delete responseCallbacks[message.responseId]; } else { ... ... ... } }); }
|
3 BridgeWebView
3.1 flushMessageQueue
native 读取 js 的命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| void flushMessageQueue() { if (Thread.currentThread() == Looper.getMainLooper().getThread()) { loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
@Override public void onCallBack(String data) { List<Message> list = null; try { list = Message.toArrayList(data); } catch (Exception e) { e.printStackTrace(); return; } if (list == null || list.size() == 0) { return; } for (int i = 0; i < list.size(); i++) { Message m = list.get(i); String responseId = m.getResponseId(); if (!TextUtils.isEmpty(responseId)) { ... ... ... ...
} else { CallBackFunction responseFunction = null; final String callbackId = m.getCallbackId(); if (!TextUtils.isEmpty(callbackId)) { responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { Message responseMsg = new Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data); queueMessage(responseMsg); } }; } else { responseFunction = new CallBackFunction() { @Override public void onCallBack(String data) { } }; } BridgeHandler handler; if (!TextUtils.isEmpty(m.getHandlerName())) { handler = messageHandlers.get(m.getHandlerName()); } else { handler = defaultHandler; } if (handler != null){ handler.handler(m.getData(), responseFunction); } } } } }); } }
|
这里可以看大了,给 js 反馈回调的时候:
1 2 3
| Message responseMsg = new Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data);
|
js 传入的 callbackId 被设置到了 responseId 上了;
3.2 loadUrl
参数 jsUrl 是 javascript:WebViewJavascriptBridge._fetchQueue();
1 2 3 4 5 6
| public void loadUrl(String jsUrl, CallBackFunction returnCallback) { this.loadUrl(jsUrl); responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); }
|
这里有分析过,对 js 命令做了处理,生成了 key:
javascript:WebViewJavascriptBridge._fetchQueue(); –> _fetchQueue
用于保存回调;
3.3 handlerReturnData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void handlerReturnData(String url) { String functionName = BridgeUtil.getFunctionFromReturnUrl(url); CallBackFunction f = responseCallbacks.get(functionName); String data = BridgeUtil.getDataFromReturnUrl(url); if (f != null) { f.onCallBack(data); responseCallbacks.remove(functionName); return; } }
|
触发前面的 _fetchQueue 对应的回调;
3.4 queueMessage
加入 message list:
1 2 3 4 5 6 7 8
| private void queueMessage(Message m) { if (startupMessage != null) { startupMessage.add(m); } else { dispatchMessage(m); } }
|
3.5 dispatchMessage
native 给 js 发送消息的关键点,参数 message 是一个消息对象!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void dispatchMessage(Message m) { String messageJson = m.toJson(); messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'"); messageJson = messageJson.replaceAll("%7B", URLEncoder.encode("%7B")); messageJson = messageJson.replaceAll("%7D", URLEncoder.encode("%7D")); messageJson = messageJson.replaceAll("%22", URLEncoder.encode("%22")); String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { 【-->*2.5】执行 js 代码; this.loadUrl(javascriptCommand); } }
|
BridgeUtil 是一个工具类,里面主要是一些通信协议码,以及一些工具方法,native 和 H5 通信的时候,本质上是执行 js 代码:
final static String JS_HANDLE_MESSAGE_FROM_JAVA =
“javascript:WebViewJavascriptBridge._handleMessageFromNative(‘%s’);”;
可以看到,执行的 js 代码如下:
javascript:WebViewJavascriptBridge._handleMessageFromNative(JsonString of Message);
我相信大家知道,这个方法将进入通信协议 js 文件了!